/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package poker.game;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Benjamin L. Brodie <blbrodie@gmail.com>
 */
public class Minimax{
    private boolean preFlop;
    private List<Integer> parameterList = null;
    private MinimaxEvaluationFunction eval;
    int count = 0;

    
    public Minimax(MinimaxEvaluationFunction eval){
        this.eval = eval;
    }
    
    /**
     * Returns the best next move according to the function. 
     * 
     * @param level the level of the round -- ie, first move is level one, second
     * is level 2, etc
     * 
     * 
     * @param p1_chips the chips of the current player
     * @param p2_chips the chips of the opponent
     * @param preFlop true if the game is in preFlop
     * @param parameterList the list of parameters to be sent to the evaluation function
     * @return the best move
     */
    public Move nextMove(int level, int p1_chips, int p2_chips, boolean preFlop, List<Integer> parameterList){
        this.preFlop = preFlop;
        this.parameterList = parameterList;
        
        Map<Move,Double> decisions = new EnumMap<Move,Double>(Move.class);
        List<Move> nextMoves = getNextDecisions(level, p1_chips, p2_chips);
        
        //this is where minimax is called and evaluation is returned
        for (Move move : nextMoves){
            
            if (move == Move.FOLD){
                decisions.put(move, minimax(move, level + 1, p1_chips, p2_chips));
            }
            else if (move == Move.CHECK){
                assert p1_chips == p2_chips: "level: " + level + ", p1_chips: " + p1_chips +
                        ", p2_chips: " + p2_chips + ", preFlop" + preFlop;
                decisions.put(move, minimax(move, level + 1, p1_chips, p2_chips));
            }
            else if (move == Move.CALL){
                assert p2_chips > p1_chips;
                decisions.put(move, minimax(move, level + 1, p2_chips, p2_chips));
            }
            else if (move == Move.RAISE){
                assert p2_chips > p1_chips ||
                        p2_chips == p1_chips && level == 0: 
                        "level: " + level + ", p1_chips: " + p1_chips +
                        ", p2_chips: " + p2_chips + ", preFlop" + preFlop;
                decisions.put(move, minimax(move, level + 1, p2_chips + 20, p2_chips));
            }
            
        }
        
        
        //get the maximum entry -- ie greatest move that gives greatest evaluation
        Map.Entry<Move,Double> maxEntry = null;
        for (Map.Entry<Move,Double> entry : decisions.entrySet()){
            if (maxEntry == null || entry.getValue() > maxEntry.getValue()){
                maxEntry = entry;
            }
        }
        
        //return the best move
        return maxEntry.getKey();
        
        
        
    }
    
    private List<Integer> addChipsToParameters(int p1_chips, int p2_chips){
        List<Integer> paramPlusChips = new ArrayList<Integer>(parameterList);
        paramPlusChips.add(p1_chips);
        paramPlusChips.add(p2_chips);
        
        return paramPlusChips;
    }
    

    
    private double minimax(Move decision, int level, int p1_chips, int p2_chips){
//        System.out.println();
//        for (int i = 0; i < level; i++){
//            System.out.print(" ");
//        }
//        
//        if (level % 2 != 0){
//            System.out.print("[p1:" + decision);
//        }else{
//            System.out.print("[p2:" + decision);
//          }
        
        
        
        
        
        
        if (decision == Move.FOLD){
            
            assert addChipsToParameters(p1_chips, p2_chips).size() == 19: "wrong size params";
//            System.out.print(": "+ eval.evaluate(addChipsToParameters(p1_chips, p2_chips)) + "]");
            
            
            
            return eval.evaluate(addChipsToParameters(p1_chips, p2_chips)); //all folds terminate
        }
        
        else if (decision == Move.CALL && level != 1){
           // System.out.print(": "+ eval.evaluate(addChipsToParameters(p1_chips, p2_chips)) + "]");
            
            assert addChipsToParameters(p1_chips, p2_chips).size() == 19: "wrong size params";
            
             return eval.evaluate(addChipsToParameters(p1_chips, p2_chips));
            //calls terminate except round 1, level 1, and level 1 won't come it
            //with a call unless it is the first round
        }
                
        else if (decision == Move.CHECK && level != 1){
             //System.out.print(": "+ eval.evaluate(addChipsToParameters(p1_chips, p2_chips)) + "]");
             
             assert addChipsToParameters(p1_chips, p2_chips).size() == 19: "wrong size params";
            
            
             return eval.evaluate(addChipsToParameters(p1_chips, p2_chips));
        }
                
        else{
        List<Double> values = new ArrayList<Double>();
        List<Move> opponentMoves = getNextDecisions(level, p2_chips, p1_chips);
        
        for (Move move : opponentMoves){
            //These are the opponents moves --- so the a raise is p1+20 in the 
            //opponents corresponding slot for chips.
            
            if (move == Move.CHECK){
                assert p2_chips == p1_chips: "opponent checked but the chips are not equal";
                values.add(-minimax(move, level + 1, p2_chips, p1_chips));
            }
            else if (move == Move.CALL){
                assert p1_chips > p2_chips;
                values.add(-minimax(move, level + 1, p1_chips, p1_chips));
            }
            else if (move == Move.RAISE){
                values.add(-minimax(move, level + 1, p1_chips + 20, p1_chips));
            }
            else if (move == Move.FOLD){
                values.add(-minimax(move, level + 1, p2_chips, p1_chips));
            }
            
            
        }
        
        //System.out.print("]");
        return Collections.max(values);
        
        
        }
    }
        

    /**
     * returns a list of next possible decisions
     * @param level
     * @param p1_chips
     * @param p2_chips
     * @return 
     */
        
     private List<Move> getNextDecisions(int level, int p1_chips, int p2_chips) {


        //exceptions at teh beginning of rounds 
        if (level == 1 && p1_chips == p2_chips){
            return Arrays.asList(Move.CHECK, Move.RAISE);
        }



        if (level == 0 && !preFlop) {
            return Arrays.asList(Move.CHECK, Move.RAISE);
        } 
                
        //normal rules, not beginning of round
        else if (p2_chips % 80 == 0 && level > 1) {
            return Arrays.asList(Move.FOLD, Move.CALL);
            
        } 
        
        else {
            return Arrays.asList(Move.FOLD, Move.CALL, Move.RAISE);
        }
    }
    
    

   
    
  
    
        
            

  
    
    
    
}

